Andy:
Jason:
Tony:
+
~
|
!!
?:
Lai:
Kai:
Chris:
Henry
mango:
turtle:
Jimmy:
強制轉型從以前到現在都具有爭議,有些人認為他是 JS 的缺陷,但對於一些怪異的部分,我們應該徹頭徹尾去了解它的優點與缺點。
【 強制轉型 】
定義:將一個值從某個型別轉換成另一個型別的動作,如果明確地進行,通常就叫做型別轉換,而如果隱含地進行,就稱為強制轉型。
特徵:在 JavaScript 中強制轉型的結果,永遠都會是純量的基本型別值,像是 String、Number 或 Boolean,而不是 Object、Function 之類的複雜的值。
以上多數人通稱為「coercion」。所以區分的方式分為兩種:
明確的強制轉型(explicit coercion):可明顯看出某個型別轉換動作。
隱含的強制轉型(implicit coercion):型別轉換動作是某個刻意進行其他作業所產生的較不明顯的副作用。
var a = 42;
var b = a + ""
// 感覺程式的目的是要做變數與字串的相加合併(或相加?),但他自己另外進行的轉型。
var c = String(a)
// 目的只有一個:就是要轉型。
在探討「明確」和「隱含」前,必須先瞭解值如何轉型為某個基本型別值。
ES5 規格的 section 9 定義了幾個抽象運算,並附有值轉換的規則。
現在主要了解 ToString、ToNumber 及 ToBoolean 身上,也可以看一下 ToPrimitive。
任何非 string 的值被強制轉型為 string 表示值,轉換過程是由 section 9.8 中的 ToString 抽象運算來處理。
別值 | 轉字串結果 |
---|---|
null | "null" |
undefined | "undefined" |
true | "true" |
123(一般數字型別) | "123" |
var a = 1.07 * 1000 * 1000 * 1000 ..
// 1.07 乘以 1000 七次(21個零)
a.toString();
//"1.07e+21"
對物件來說,使用 Object.prototype.toString()
,會回傳內部的 [[Class]]
,如果物件有他自己的 toString
方法,而你又把這個物件當作 string 來用,那他的 toString
方法就會被自動呼叫。
注意:如果一個物件被強制轉型成一個字串,那過程會經過 ToPrimitive 的抽象處理,詳見ECMA 5 section9.1。
ToNumber 後面會講,現在先跳過。
補充說明
陣列轉字串,會先將陣列的值字串化後,再以,
連接各個值。
toString
可以被明確地被呼叫,或是當一個不是字串的值被用在字串的情境中,他就會被自動呼叫。
Array 本身有 toString
依照 prototype toString 的方法會覆蓋物件的toString
。
使用 JSON.stringify(..)
,他會將一個值序列化為一個 JSON 相容的字串。這種序列化和強制轉型不完全相同,但它和 ToString 規則有關連,所以稍微說一下。
JSON.stringify( 42 ); // "42"
JSON.stringify( "42" ); // ""42"" (一个包含雙引號的字串)
JSON.stringify( null ); // "null"
JSON.stringify( true ); // "true"
任何 JSON-safe 的值都可以被 JSON.stringify(..)
。
JSON.stringify( undefined );
// undefined
JSON.stringify( function(){} );
// undefined
JSON.stringify( [1,undefined,function(){},4] );
// "[1,null,null,4]"
JSON.stringify( { a:2, b:function(){} } );
// "{"a":2}"
JSON 字串化的特殊行為:一個物件值定義有 toJSON(),那此方法就會被先呼叫,以取得一個用於序列化的物件。
如果想字串化一個非 JSON-safe 的值,那可以定義一個 toJSON() ,回傳一個該物件 JSON-safe 的版本。
var o = { };
var a = {
b: 42,
c: o,
d: function(){}
};
// 在 a 內部建立一個循環參考
o.e = a;
// 會對 JSON.stringify( a ); 丟出錯誤
// 定義一個 JSON 值用於序列化
a.toJSON = function() {
// 序列化過程只含有 b 特性
return { b: this.b };
};
JSON.stringify( a ); // "{"b":42}"
toJSON 不是轉為 JSON 字串,而是轉為一個適合用於字串化的 JSON-safe 值。
正確觀念範例:
var a = {
val: [1,2,3],
toJSON: function(){
return this.val.slice( 1 );
}
};
JSON.stringify( a ); // "[2,3]"
常見誤解範例:
var b = {
val: [1,2,3],
toJSON: function(){
return "[" +
this.val.slice( 1 ).join() +
"]";
}
// 字串化所回傳的字串,而非陣列本身。
};
JSON.stringify( b ); // ""[2,3]""
怪異的部分
如果有人知道可以告訴我,感謝。
JSON.stringify(...)
的第二個引數:undefined
,否則就回傳所提供的值。var a = {
b: 42,
c: "42",
d: [1,2,3]
};
JSON.stringify( a, ["b","c"] ); // "{"b":42,"c":"42"}"
JSON.stringify( a, function(k,v){
if (k !== "c") return v;
} );
// "{"b":42,"d":[1,2,3]}"
JSON.stringify(...)
的第三個引數:var a = {
b: 42,
c: "42",
d: [1,2,3]
};
JSON.stringify( a, null, 3 );
// "{
// "b": 42,
// "c": "42",
// "d": [
// 1,
// 2,
// 3
// ]
// }"
JSON.stringify( a, null, "-----" );
// "{
// -----"b": 42,
// -----"c": "42",
// -----"d": [
// ----------1,
// ----------2,
// ----------3
// -----]
// }"
JSON.stringify
,而那個物件值上有一個 toJSON
方法,這個 toJSON
在字串化前會被自動呼叫,而這個物件可以算是隱含的強制轉型為 JSON-safe。(因為我們沒有主動呼叫 toJSON)任何不是數字型別而被當作數字來用,例如數學運算,ES5 有定義了 ToNumber 的抽象運算。
轉型前 | 轉型後 |
---|---|
true | 1 |
false | 0 |
undefined | NaN |
null | 0 |
對字串來說,ToNumber 跟數值字面值(第六章)的規則和語法很像。
如果非數字字面值轉型失敗,結果就是 NaN,差別在於 0 前綴的八進位數字不會被當作八進位,而是十進位。
(八進位是 ES5 之前的事)
它會先被轉為基本型別值等效值,而產生結果,而這個結果值會根據剛提到的規則被強制轉換成數字。
為了轉換基本型別的等效值,ToPrimitive 抽象運算:
valueOf()
方法,如果有就回傳一個基本型別值,那個值就會被用於強制轉型,看有沒有基本型別值。toString()
就會提供用於強制轉型的值,看有沒有基本型別值。範例:
var a = {
valueOf: function(){
return "42";
}
};
var b = {
toString: function(){
return "42";
}
};
var c = [4,2];
c.toString = function(){
return this.join( "" ); // "42"
};
Number( a ); // 42
Number( b ); // 42
Number( c ); // 42
Number( "" ); // 0
Number( [] ); // 0
Number( [ "abc" ] ); // NaN